package NetBeansProjects.Indexer3.src.model.newmodel;

import java.util.Iterator;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.TreeSet;

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

//package searcherPackage.Index;
//
//import java.util.Iterator;
//import java.util.Set;
//import java.util.StringTokenizer;
//import java.util.TreeSet;

/**
 *
 * @author i_khri
 */
public class PostingsList implements Iterable<PostingsList.Entry>{

    /**
     * Class constructor. Create a new tree set upon construction.
     */
    public PostingsList(){
        entries = new TreeSet<Entry>();
    }

    /**
     *
     * @param theString create a posting list from this string.
     * @return the posting list formed from the given string
     */
    public static PostingsList fromIndexString(String theString){
        PostingsList pList = new PostingsList();

        int beginIndex = theString.indexOf('[');
        int endIndex = theString.indexOf(']');

        String szPostingsList = theString.substring(beginIndex + 1, endIndex);
        szPostingsList = szPostingsList.replace(',', ' ');

        StringTokenizer st = new StringTokenizer(szPostingsList);
        while (st.hasMoreTokens()) {
            String token = st.nextToken();
            String[] arr = token.split(":");
            int post = Integer.parseInt(arr[0]);
            double tf = Double.parseDouble(arr[1]);
            pList.addEntry(new PostingsList.Entry(post, tf));
        }

        return pList;
    }



    public static PostingsList intersection(PostingsList pl1, PostingsList pl2){
        PostingsList result = new PostingsList();
        if(pl1 != null){
            if(pl2 != null){
                result.entries.addAll(pl1.entries);
                result.entries.retainAll(pl2.entries);
                return result;
            }else{ // pl1 != null, pl2 == null
                return pl1;
            }
        }else{
            if(pl2 != null){ // pl1 == null, pl2 != null
                return pl2;
            }else{
                return result; // return empty postings list if both parameters are null
            }
        }
    }



    /**
     * @param pl1 posting list to unite
     * @param pl2 posting list to unite
     * @return the united lists
     * Binary union operation based on Java Set's {@code addAll} method. Entries are added
     * based only on equality of document id's. Other information in {@code PostingsList.Entry}
     * is ignored. As a result, if both postings lists contain entries with
     * same document id, for example, term frequencies for entry with this document
     * id in the resulting postings list will not be added.
     */
    public static PostingsList union(PostingsList pl1, PostingsList pl2){
        PostingsList result = new PostingsList();
        if(pl1 != null){
            if(pl2 != null){
                result.entries.addAll(pl1.entries);
                result.entries.addAll(pl2.entries);
                return result;
            }else{ // pl1 != null, pl2 == null
                return pl1;
            }
        }else{
            if(pl2 != null){ // pl1 == null, pl2 != null
                return pl2;
            }else{
                return result; // return empty postings list if both parameters are null
            }
        }
    }



    /**
     * @param pl2 second posting list to merge
     * @param pl1 first posting list to merge
     * Unlike {@code union}, complete, 'deep', merging method that not only
     * creates a binary union, but also considers other fields of {@code PostingsList.Entry}.
     * When two entries in different postings lists have same document id and are
     * being merged, their term frequencies are also added.
     */
    public static PostingsList merge(PostingsList pl1, PostingsList pl2){
        PostingsList result = new PostingsList();
        if(pl1 != null){
            if(pl2 != null){
                /* An attempt to slightly increase peformance by iterating over
                 * the smallest list.
                 * The idea is that, supposedly, fast addAll will handle the
                 * larger list, while leaving the shorter one for mergeWith
                 * to iterate over.
                 * Should work if time complexity addAll is less than O(n) and
                 * this.entries is a TreeSet and time complexity of TreeSet's
                 * floor function is less than O(n) [see getEntryByDocId for reasons].
                 * otherwise no performace increase is gained.
                 */
                if(pl1.size() > pl2.size()){
                    result.entries.addAll(pl1.entries);
                    result.mergeWith(pl2);
                }else{
                    result.entries.addAll(pl2.entries);
                    result.mergeWith(pl1);
                }
                return result;
            }else{ // pl1 != null, pl2 == null
                return pl1;
            }
        }else{
            if(pl2 != null){ // pl1 == null, pl2 != null
                return pl2;
            }else{
                return result; // return empty postings list if both parameters are null
            }
        }
    }


    /**
     *
     * @param other the other postin list to merge
     */
    private void mergeWith(PostingsList other){
        for(Entry entry : other){
            // if the entry from other postings list exists in this one, increase
            // tf of this entry by tf of other
            if(this.entries.contains(entry)){
                Entry fromEntries = getEntryByDocId(entry.docId);
                fromEntries.mergeWith(entry);

            // else add other entry to this postings list
            }else{
                this.entries.add(entry);
            }
        }
    }



    /**
     * ATTENTION: Can return null if no entries with requested docId were found.
     * @param docId the wanted docId
     * @return the entry with the respective docId 
     */
    public Entry getEntryByDocId(int docId){
        Entry target = new Entry(docId);
        Entry result = null;
        if(this.entries instanceof TreeSet<?>){
            Entry candidate = ((TreeSet<Entry>) this.entries).floor(target);
            if(candidate != null && candidate.equals(target)){
                result = candidate;
            }
        }else{
            for(Entry candidate : this.entries){
                if(candidate.equals(target)){
                    result = candidate;
                    break;
                }
            }
        }
        return result;
    }



    public void addDocId(int docId){
        // look up a doc id in the set,
        // insert if not found,
        // increment tf if found.
        if(!entries.contains(new Entry(docId))){
            entries.add(new Entry(docId, 1));
        }else{
            Entry e = new Entry(docId);
            for(Entry entry : entries){
                if(entry.equals(e)){
                    entry.termFrequency++;
                }
            }
        }
    }


    /**
     *
     * @return the iterator of the entries. This will allow you to iterate
     * through the results.
     */
    public Iterator<Entry> iterator() {
        return entries.iterator();
    }
    

    /**
     * Nested class  to take care of comparisons
     */
    public static class Entry implements Comparable<PostingsList.Entry>{

        // should not be used outside of the PostingsList class.
        private Entry(int docId){
            this.docId = docId;
            this.termFrequency = 0;
        }


        public Entry(int docId, double termFreq){
            this.docId = docId;
            this.termFrequency = termFreq;
        }


        // accessors
        public int getDocId() {
            return docId;
        }



        public double getTermFrequency() {
            return termFrequency;
        }



        public void setDocId(int docId) {
            this.docId = docId;
        }



        // mutators
        public void setTermFrequency(double termFrequency) {
            this.termFrequency = termFrequency;
        }



        public int compareTo(PostingsList.Entry o) {
            return docId - o.docId;
        }



        @Override
        public int hashCode() {
            int hash = 5;
            hash = 61 * hash + this.docId;
            return hash;
        }



        @Override
        public boolean equals(Object obj) {
            if (obj == null) {
                return false;
            }
            if (getClass() != obj.getClass()) {
                return false;
            }
            final Entry other = (Entry) obj;
            if (this.docId != other.docId) {
                return false;
            }
            return true;
        }



        @Override
        public String toString(){
            return docId + ":" + termFrequency;
        }



        private boolean mergeWith(Entry other){
            if(this.docId == other.docId){
                this.termFrequency += other.termFrequency;
                return true;
            }
            return false;
        }



        private int docId;
        private double termFrequency;
    }


    
    public void addEntry(Entry entry){
        entries.add(entry);
    }



    public int size(){
        return entries.size();
    }



    public boolean isEmpty(){
        return entries.isEmpty();
    }



    public boolean contains(Entry entry){
        return entries.contains(entry);
    }


    @Override
    public String toString(){
        return entries.toString();
    }



    private Set<Entry> entries;
}
